<?php
/**
 * Copyright 2009 Joe Dotoff
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * A base socket class
 * Used to connect to a server socket
 *
 * @catlog   PDog
 * @package  Socket
 * @author   Joe D. <nonultimate@gmail.com>
 * @license  Apache License Version 2.0
 * @version  $Id: Socket.php 33 2009-04-21 01:03:44Z nonultimate $
 */
class Socket
{
    /**
     * Socket instance
     *
     * @var resource
     */
    protected $_socket;
    /**
     * The address for the socket
     *
     * @var string
     */
    protected $_addr;
    /**
     * The port for the socket
     *
     * @var int
     */
    protected $_port;
    /**
     * The binding state of the socket
     *
     * @var bool
     */
    protected $_isBound;
    /**
     * The connecting state of the socket
     */
    protected $_isConnected;
    /*
     * Constructor
     *
     * @param int  $family  address family, AF_INET, AF_INET6,AF_UNIX
     * @param int  $type    socket type stream or datagram, SOCK_STREAM, SOCK_DGRAM
     * @param int  $proto   socket protocol, SOL_TCP, SOL_UDP
     */
    public function __construct($family = null, $type = SOCK_STREAM, $proto = SOL_TCP)
    {
        // Control error output level
        error_reporting(E_ERROR);
        // Set time to infinite execution
        set_time_limit(0);
        // Try to load extension if not loaded
        if (!extension_loaded('sockets')) {
            $is_loaded = false;
            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
                $is_loaded = dl('php_sockets.dll');
            } else {
                $is_loaded = dl('sockets.so');
            }
            if (!$is_loaded) {
                die('Sockets extension not loaded');
            }
        }
        // Initialize
        $this->_addr = '';
        $this->_port = '';
        $this->_isBound = false;
        $this->_isConnected = false;

        if (null !== $family) {
            $this->_socket = socket_create($family, $type, $proto);
            socket_bind($this->_socket, '0.0.0.0');
            $this->_isBound = true;
        }
     }
    /**
     * Bind the socket to a local address
     *
     * @param  array|object $address  keys as:
     *     'family', the protocol family, AF_INET,AF_INET6,AF_UNIX
     *     'type',   the type of the socket, SOCK_STREAM,SOCK_DGRAM,SOCK_SEQPACKET,SOCK_RAW,SOCK_RDM
     *     'proto',  the protocol of the socket, SOL_TCP,SOL_UDP
     *     'addr',   IP address or unix socket name
     *     'port',   the port for the address if it is a IP address
     * @return bool
     */
    public function bind($address)
    {
        if ($this->isBound()) {
            return false;
        }
        if (!is_array($address) or !is_object($address)) {
            return false;
        }
        $family = AF_INET;
        $type = SOCK_STREAM;
        $proto = SOL_TCP;
        $addr = '0.0.0.0';
        try {
            if (is_array($address) or is_object($address)) {
                foreach ($address as $key => $value) {
                    $$key = $value;
                }
            }
            $this->_addr = $addr;
            $this->_socket = socket_create($family, $type, $proto);
            if (isset($port)) {
                $this->_port = $addr;
                socket_bind($this->_socket, $addr, $port);
            } else {
                socket_bind($this->_socket, $addr);
            }
            $this->_isBound = true;

            return true;
        } catch (Exception $e) {
            return false;
        }
    }
    /**
     * Close the socket resource
     *
     * @return bool
     */
    public function close()
    {
        if (!$this->isClosed()) {
            socket_close($this->_socket);
        }
        $this->_socket = null;

        return true;
    }
    /**
     * Connect to the host
     *
     * @param string $host  The host to connect
     * @param int    $port  The port to connect
     */
    public function connect($host, $port = null)
    {
        try {
            if (null === $port) {
                socket_connect($this->_socket, $host);
            } else {
                socket_connect($this->_socket, $host, $port);
            }
            $this->_isConnected = true;

            return true;
        } catch (Exception $e) {

        }

        return false;
    }
    /**
     * Create a socket from server socket
     * You should never use this directly, use ServerSocket.accept() instead.
     *
     * @param resource  $sock
     * @return void
     */
    public function createFrom($sock)
    {
        if (is_resource($sock)) {
            socket_getpeername($sock, $host, $port);
            $this->_socket = $sock;
            if (!empty($host)) {
                $this->_addr = $host;
            }
            if (!empty($port)) {
                $this->_port = $port;
            }
            $this->_isBound = true;
            $this->_isConnected = true;
        }
    }
    /**
     * Returns the local address of this server socket
     *
     * @return string
     */
    public function getAddress()
    {
        return $this->_addr;
    }
    /**
     * Returns the local address of this server socket
     *
     * @return string
     */
    public function getPort()
    {
        return $this->_port;
    }
    /**
     * Returns the binding state of the socket
     *
     * @return bool
     */
    public function isBound()
    {
        return $this->_isBound;
    }
    /**
     * Returns the close state of the socket
     *
     * @return bool
     */
    public function isClosed()
    {
        if (is_resource($this->_socket)) {
            return false;
        }

        return true;
    }
    /**
     * Returns the connecting state of the socket
     *
     * @return bool
     */
    public function isConnected()
    {
        return $this->_isConnected;
    }
    /**
     * Read bytes of data from the socket
     *
     * @param int   $length  The size to read from the socket
     * @return string
     */
    public function read($length)
    {
        if (!$this->isClosed()) {
            return socket_read($this->_socket, $length, PHP_NORMAL_READ);
        }

        return '';
    }
    /**
     * Receive data from the connected socket
     *
     * @param  string  $buffer  Buffer string to read
     * @param  int     $length  Size of data to read
     * @param  int     $flag    MSG_OOB,MSG_EOR,MSG_EOF,MSG_DONTROUTE
     * @return int     Size of data received
     */
    public function recv(&$buffer, $length, $flag)
    {
        if (!$this->isClosed()) {
            return socket_recv($this->_socket, $buffer, $length, $flag);
        }

        return 0;
    }
    /**
     * Receive data from a socket whether it is connected or not
     *
     * @param  string  $buffer  Buffer string to read
     * @param  int     $length  Size of data to read
     * @param  int     $flag    MSG_OOB,MSG_EOR,MSG_EOF,MSG_DONTROUTE
     * @param  string  $addr    IP address or unix socket name
     * @param  int     $port    The port to connect
     * @return int     Size of data received
     */
    public function recvfrom(&$buffer, $length, $flag, &$addr, &$port = null)
    {
        if (null === $port) {
            return socket_recvfrom($this->_socket, $buffer, $length, $flag, $addr);
        } else {
            return socket_recvfrom($this->_socket, $buffer, $length, $flag, $addr, $port);
        }
    }
    /**
     * Send data to the connected socket
     *
     * @param  string  $buffer  Buffer string to read
     * @param  int     $length  Size of data to read
     * @param  int     $flag    MSG_OOB,MSG_EOR,MSG_EOF,MSG_DONTROUTE
     * @return int     Size of data sent to remote socket
     */
    public function send($buffer, $length, $flag)
    {
        if (!$this->isClosed()) {
            return socket_send($this->_socket, $buffer, $length, $flag);
        }

        return 0;
    }
    /**
     * Send data to a socket whether it is connected or not
     *
     * @param  string  $buffer  Buffer string to read
     * @param  int     $length  Size of data to read
     * @param  int     $flag    MSG_OOB,MSG_EOR,MSG_EOF,MSG_DONTROUTE
     * @param  string  $addr    IP address or unix socket name
     * @param  int     $port    The port to connect
     * @return int     Size of data sent to remote socket
     */
    public function sendto($buffer, $length, $flag, $addr, $port = null)
    {
        if (null === $port) {
            return socket_sendto($this->_socket, $buffer, $length, $flag, $addr);
        } else {
            return socket_sendto($this->_socket, $buffer, $length, $flag, $addr, $port);
        }
    }
    /**
     * Disable input stream for the socket
     *
     * @return void
     */
    public function shutdownInput()
    {
        if (!$this->isClosed()) {
            socket_shutdown($this->_socket, 0);
        }
    }
    /**
     * Disable output stream for the socket
     *
     * @return void
     */
    public function shutdownOutput()
    {
        if (!$this->isClosed()) {
            socket_shutdown($this->_socket, 1);
        }
    }
    /**
     * Write data to the socket
     *
     * @param string    $buffer  The data to write
     * @return void
     */
    public function write($buffer)
    {
        if (!$this->isClosed()) {
            socket_write($this->_socket, $buffer);
        }
    }
}
?>